---
--类名: NpcManager.lua
--创建日期: 2017-12-12
--创建人: 陈小虎
--功能描述: 
--修改内容:
--修改日期:   
--修改人: galileoliu
--修改备注: 
--
local Npc = import("..models.Npc")
local Area = import("..models.Area")
local NPC = require("app.public.dao.NPC")

local NpcManager = class("NpcManager")

local NodeGetPositionFunc = cc.Node.getPosition
local NodeSetPositionFunc = cc.Node.setPosition
local NodeGetPositionXFunc = cc.Node.getPositionX
local NodeSetPositionXFunc = cc.Node.setPositionX
local NodeGetPositionYFunc = cc.Node.getPositionY
local NodeSetPositionYFunc = cc.Node.setPositionY
local NodeIsVisibleFunc = cc.Node.isVisible
local NodeSetVisibleFunc = cc.Node.setVisible
local NodeToWorldPosition = cc.Node.toWorldPosition
local NodeGetScaleFunc = cc.Node.getScale
local NodeSetScaleFunc = cc.Node.setScale
local NodeGetScaleXFunc = cc.Node.getScaleX
local NodeSetScaleXFunc = cc.Node.setScaleX
local NodeGetScaleYFunc = cc.Node.getScaleY
local NodeSetScaleYFunc = cc.Node.setScaleY

local NpcAllVisibility = Npc.allVisibility
local NpcAllNameNodeVisibility = Npc.allNameNodeVisibility
local NpcCurrentMarkVisibility = Npc.currentMarkVisibility
local NpcUpdateFrame = Npc.updateFrame
local NpcUpdateFrameLower = Npc.updateFrameLower
local NpcUpdateHeadSignMark = Npc.updateHeadSignMark
local NpcAdjustSounds = Npc.adjustSounds
local NpcStopShowUpHead = Npc.stopShowUpHead
local NpcUpdateSecret = Npc.updateSecret
local NpcIsPlayEvents = Npc.isPlayEvents
local NpcGetEvents = Npc.getEvents
local NpcIsDungeon = Npc.isDungeon
local NpcIsDoingEvents = Npc.isDoingEvents
local NpcIsFirstDungeonSwitch = Npc.isFirstDungeonSwitch
local NpcSetPlayEvents = Npc.setPlayEvents

local AreaIsPlayingEvents = Area.isPlayingEvents
local AreaIsPublic = Area.isPublic

local ToString = tostring
local ToNumber = tonumber
local MathFloor = math.floor
local MathAbs = math.abs

local npcUidClientPrefix = "clientNpcUid_"

local TeamModel

function NpcManager:ctor()
    cc(self):addComponent("components.behavior.EventProtocol"):exportMethods()

    self._loadSaveNpcs = {}
    self._serviceInsertNpcs = {}

    self._areas = {}
    self._npcs = {}
    self._resourcePointNpcUidList = {}
    self._grayBattleNpcUidList = {}

    self._uidGenerator = 0

    self:clear()
end

function NpcManager:init()
	TeamModel = app:getInst("TeamModel")
    UD:onChanged("grayBattleInfoUpdate",
        function()
            self:updateGrayBattleNpcs()
        end)
end

function NpcManager:setBaseNode(bNode)
    self._baseNode = bNode
end

function NpcManager:getBaseNode()
    return self._baseNode
end

function NpcManager:clear()
    self:clearAllNpcsFromVec()
	-- self:clearServiceInsertNpcs()
	self:clearArea()
	self:clearNpc()
	self:clearNpcConfigs()
    self:setFadeNpcsForBattle(false)
end

function NpcManager:clearNpcConfigs()
    self._mapsetBaseConfigs = {}
    self._mapsetShowConfigs = {}
    self._oreConfigs = {}
    self._oreRoastConfigs = {}
    self._bnpcConfigs = {}

end

function NpcManager:loadAllNpcConfigs(mapId)
    self:clearNpcConfigs()
    
    local configs = GD:queryMapsetBaseByMapId(mapId)
    for k,v in pairs(configs) do
        self._mapsetBaseConfigs[v.ID] = v
    end
    configs = GD:queryMapsetShowByMapId(mapId)
    for k,v in pairs(configs) do
        self._mapsetShowConfigs[v.ID] = v
    end
    configs = GD:queryOreByMapId(mapId)
    for k,v in pairs(configs) do
        self._oreConfigs[v.ID] = v
    end
    configs = GD:queryOreByMapIdAndType(mapId,3)
    for k,v in pairs(configs) do
        self._oreRoastConfigs[v.ID] = v
    end
end

function NpcManager:getMapsetBaseConfigs()
    return self._mapsetBaseConfigs
end

function NpcManager:getMapsetShowConfigs()
    return self._mapsetShowConfigs
end

function NpcManager:getOreConfigs()
    return self._oreConfigs
end

function NpcManager:getRoastConfigs()
    return self._oreRoastConfigs
end

function NpcManager:getNpcConfigById(npcId)
    local npcConfig = self._mapsetBaseConfigs[ToString(npcId)]
    if not npcConfig then
        npcConfig = self._mapsetShowConfigs[ToString(npcId)]
        if not npcConfig then
            npcConfig = self._oreConfigs[ToString(npcId)]
        end
    end
    return npcConfig
end

function NpcManager:loadBNpcConfigs()
    local configs = GD:queryBNpcs()
    for k,v in pairs(configs) do
        self._bnpcConfigs[v.ID] = v
    end
end

function NpcManager:getBNpcConfigById(npcId)
    return self._bnpcConfigs[ToString(npcId)]
end

function NpcManager:clearArea()
    self._areas = {}
end

function NpcManager:addArea(area)
    if not self:getArea(area:getId()) then
        self._areas[ToString(area:getId())] = area
    end
end

function NpcManager:getArea(npcId)
    return self._areas[ToString(npcId)]
end

function NpcManager:removeAreaByUid(uid)
    for k,v in pairs(self._areas) do
        local area = v
        if area and area:getUid() == uid then
            self._areas[k] = nil
        end
    end
end

function NpcManager:deleteAreaById(npcId)
    if self._areas[ToString(npcId)] then
        self._areas[ToString(npcId)] = nil
    end
end

function NpcManager:getAreaVec()
    return self._areas
end

function NpcManager:clearAreaDoingStatus()
    for k,v in pairs(self._areas) do
        if v then
            v:playingEvents(false)
        end
    end
end

function NpcManager:deleteAreaByMainTaskId(mainTaskId)
    for k,v in pairs(self._areas) do
        local area = v
        if area and area:getUid() == nil and area:getShowMinTaskId() > mainTaskId then
            self._areas[k] = nil
        end
    end 
end

function NpcManager:clearAllServiceNpcsEvents()
    local ownerNpcsVec = self._npcs
    for k,npc in pairs(ownerNpcsVec) do
        if npc then
            npc:clearEvents()
        end
    end

    local areaVec = self:getAreaVec()
    for k,v in pairs(areaVec) do
        if v then
            v:clearEvents()
        end
    end
end

function NpcManager:deleteNotNeedMapSetBaseNpc()
    local mainTaskId = UD:getCurTaskId()

    self:deleteNpcByMainTaskId(mainTaskId)
    self:deleteAreaByMainTaskId(mainTaskId)
end

function NpcManager:deleteNpcFromService(uid)
    local npc = self:getNpcByUid(uid)
    if npc then
        if not npc:isChests() then
            self:deleteNpcByUid(uid)
            return
        end
    end
    
    self:removeAreaByUid(uid)
end

function NpcManager:clearAllNpcsFromVec()
    for i,npc in ipairs(self._loadSaveNpcs) do
        npc:release()
    end
    self:clearLoadNpcs()
end

function NpcManager:clearLoadNpcs()
    self._loadSaveNpcs = {}
end

function NpcManager:addLoadNpc(npc)
    table.insert(self._loadSaveNpcs,npc)
end

function NpcManager:getLoadNpcs()
    return self._loadSaveNpcs
end


-- function NpcManager:addServiceInsertNpc(npc)
--     if not npc then return end
--     table.insert(self._serviceInsertNpcs,npc)
-- end

-- function NpcManager:getServiceInsertNpcs()
--     return self._serviceInsertNpcs
-- end

-- function NpcManager:clearServiceInsertNpcs()
--     self._serviceInsertNpcs = {}
-- end

-- function NpcManager:setMemberFollowNpcId(npcId)
--     self._memberFollowNpcId = npcId
-- end

-- function NpcManager:getMemberFollowNpcId()
--     return self._memberFollowNpcId
-- end

function NpcManager:clearNpc()
    self._npcs = {}
    self._resourcePointNpcUidList = {}
    self._grayBattleNpcUidList = {}
    self._flagNpcs = {}
end

function NpcManager:addNpc(npc)
    if not npc then return end
    -- npc:setLinesData(MapManager:getLinesData())
    local uid = npc:getUid()
    if uid == nil then
        uid = self:generateNpcUid()
        npc:setUid(uid)
    end
    self._npcs[uid] = npc
end

function NpcManager:deleteNpcById(npcId,line)
--    print("==================> NpcManager:deleteNpcById(npcId,line)", npcId, line)
    local npcToDelete
    for uid, npc in pairs(self._npcs) do
        if not cc.isDead(npc) and npc:getId() == npcId then
            npcToDelete = npc
            break
        end
    end
    if npcToDelete then
--        print("==================> do find npc:", npcToDelete:getUid(), npcId, line)
        npcToDelete:removeAll()
        self._npcs[npcToDelete:getUid()] = nil
        self:dispatchEvent({name = "map_DeleteNpc",npc = npcToDelete,line = line})
    else
--        print("==================> do find area:", npcId, line)
        self:deleteAreaById(npcId)
    end
end

function NpcManager:deleteNpcByUid(uid)
    local npcToDelete = self._npcs[ToString(uid)]
    if npcToDelete then
        npcToDelete:removeAll()
        self._npcs[ToString(uid)] = nil
        self:dispatchEvent({name = "map_DeleteNpc",npc = npcToDelete,line = npcToDelete:getLine()})
    end
end

function NpcManager:deleteNpcByMainTaskId(mainTaskId)
    for uid,npc in pairs(self._npcs) do
        -- 之前Task生成的NPC没有uid，潜规则
        if not cc.isDead(npc) and npc:getShowMinTaskId() > mainTaskId then
            npc:removeAll()
            self._npcs[uid] = nil
            self:dispatchEvent({name = "map_DeleteNpc",npc = npc,line = npc:getLine()})
        end
    end
end

function NpcManager:removeAllNpc()
    for uid, npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:removeAll()
            self:dispatchEvent({name = "map_DeleteNpc",npc = npc})
        end
    end
    self._npcs = {}
end

function NpcManager:pauseAllNpc(b)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            if b then
                npc:pauseRecursive()
            else
                npc:resumeRecursive()
            end
        end
    end
end

function NpcManager:setNpcVisibleById(npcId, visible)
    for uid, npc in pairs(self._npcs) do
        if not cc.isDead(npc) and npc:getId() == npcId then
            NpcAllVisibility(npc,visible)
        end
    end
end

function NpcManager:mapNpcExceptUiNpcVisibility(visible)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            if npc:getUiHideType() == 1 then
                NpcAllVisibility(npc,true)
            else
                NpcAllVisibility(npc,visible)
            end
        end
    end
end

function NpcManager:allMapNpcsVisibility(visible)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) and not npc:isTaskGathered() then
            NpcAllVisibility(npc,visible)
        end
    end
end

--function NpcManager:disasterNpcVisibility(visible)
--    for uid,npc in pairs(self._npcs) do
--        if (npc:getType() >= 6 and npc:getType() <= 12)
--            or (npc:getType() >= 20 and npc:getType() <= 24) then
--            NpcAllVisibility(npc,visible)
--        end
--    end
--end

function NpcManager:oreNpcVisibility(visible)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) and npc:isOre() then
            NpcAllVisibility(npc,visible)
        end
    end
end

function NpcManager:clearAllMapNpcDoing()
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:setDoingEvents(false)
        end
    end
end

function NpcManager:resetAllNpcEventStatus()
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:setDoingEvents(false)
            npc:setPlayEvents(false)
        end
    end
end

function NpcManager:stopAllNpcMarkForEvent(stop)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:stopMarkForEvent(stop)
        end
    end
end

function NpcManager:isNpcExcuteEvents()
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            if npc:isPlayEvents() or npc:isDoingEvents() then
                return true
            end
        end
    end

    return false
end


function NpcManager:mapNpcMarkVisibility(visible)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            NpcCurrentMarkVisibility(npc,visible)
        end
    end
end

function NpcManager:mapNpcNameNodeVisibility(visible)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            NpcAllNameNodeVisibility(npc,visible)
        end
    end
end

function NpcManager:stopAllNpcUpHeadShow(isStop)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:stopShowUpHead(isStop)
        end
    end
end

function NpcManager:allNpcStandBy()
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:standBy()
        end
    end
end

function NpcManager:getNpcVec()
    return self._npcs
end

function NpcManager:getNpcByTypeId(npcId)
    for uid, npc in pairs(self._npcs) do
        if not cc.isDead(npc) and npc:getId() == npcId then
            return npc
        end
    end
end

function NpcManager:getNpcIfUidNotNil(npcId)
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc)
          and ToNumber(npcId) == ToNumber(npc:getId())
          and npc:getUid() then
            return npc
        end
    end
end

function NpcManager:getNpcByUid(uid)
    return self._npcs[ToString(uid)]
end

function NpcManager:getNpcById(npcId)
    return self:getNpcByTypeId(npcId)
end

function NpcManager:fadeInAllNpcs(time)
    local fadeTime = time or 0
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc) and npc:isVisible() then
            local action = cc.FadeIn:create(fadeTime)
            npc:excuteStopAllActions()
            npc:excuteAction(action)
        end
    end
end

function NpcManager:fadeOutAllNpcs(time)
    local fadeTime = time or 0
    for uid,npc in pairs(self._npcs) do
        if not cc.isDead(npc)
          and npc:isVisible()
          and not npc:isChestsDisappear() then
            local action = cc.FadeOut:create(fadeTime)
            npc:excuteStopAllActions()
            npc:excuteAction(action)
        end
    end
end

function NpcManager:resetNpcMask()
    if not self._npcMaskParams then return end
    for npcId,v in pairs(self._npcMaskParams) do
        if npcId == 0 then
            local myCharacter = FieldManager:getMyCharacter()
            if myCharacter then
                display.setColor(myCharacter,cc.c4b(255,255,255,255))
            end
            local puppetCharacter = FieldManager:getPuppetCharacter()
            if puppetCharacter then
                display.setColor(puppetCharacter,cc.c4b(255,255,255,255))
            end
        else
            local npc = self:getNpcById(npcId)
            if npc then
                display.setColor(npc,cc.c4b(255,255,255,255))
            end
        end
    end
    self._npcMaskParams = nil
end

function NpcManager:setSingleNpcMask(npcId,color,fadeTime,overCall)
    self._npcMaskParams = (self._npcMaskParams == nil) and {} or self._npcMaskParams
    self._npcMaskParams[npcId] = {overCall = overCall}

    local maskFadeTime = fadeTime or 0
    if maskFadeTime > 0 then
        self._npcMaskParams[npcId].startNpcMaskFade = true
        self._npcMaskParams[npcId].npcMaskTColor = color
        self._npcMaskParams[npcId].startNpcMapMaskTime = app:getTime()
        self._npcMaskParams[npcId].npcMaskFadeTime = maskFadeTime
        if npcId == 0 then
            local myCharacter = FieldManager:getMyCharacter()
            if myCharacter then
                self._npcMaskParams[npcId].mcOColor = myCharacter:getColor()
            end
            local puppetCharacter = FieldManager:getPuppetCharacter()
            if puppetCharacter then
                self._npcMaskParams[npcId].pcOColor = puppetCharacter:getColor()
            end
        else
            local npc = self:getNpcById(npcId)
            if npc then
                self._npcMaskParams[npcId].npcOColor = npc:getColor()
            end
        end
    else
        if npcId == 0 then
            local myCharacter = FieldManager:getMyCharacter()
            if myCharacter then
                display.setColor(myCharacter,color)
            end
            local puppetCharacter = FieldManager:getPuppetCharacter()
            if puppetCharacter then
                display.setColor(puppetCharacter,color)
            end
        else
            local npc = self:getNpcById(npcId)
            if npc then
                display.setColor(npc,color)
            end
        end
        self:singleNpcMaskOverCall(npcId)
    end

end

function NpcManager:singleNpcMaskOverCall(npcId)
    if self._npcMaskParams then
        local param = self._npcMaskParams[npcId]
        if param and param.overCall then
            param.overCall()
        end
        self._npcMaskParams[npcId] = nil
    end
end

function NpcManager:updateNpcMask(dt)
    if not self._npcMaskParams then return end

    local currentTime = app:getTime()
    for npcId,v in pairs(self._npcMaskParams) do
        if v.startNpcMaskFade then
            local iTime = currentTime - v.startNpcMapMaskTime
            if iTime > v.npcMaskFadeTime then
                iTime = v.npcMaskFadeTime
            end
            local percent = iTime / v.npcMaskFadeTime

            if v.mcOColor then
                local myCharacter = FieldManager:getMyCharacter()
                if myCharacter then
                    local ir = (v.npcMaskTColor.r - v.mcOColor.r) * percent
                    local ig = (v.npcMaskTColor.g - v.mcOColor.g) * percent
                    local ib = (v.npcMaskTColor.b - v.mcOColor.b) * percent
                    local cColor = {r = v.mcOColor.r + ir,g = v.mcOColor.g + ig,b = v.mcOColor.b + ib}
                    display.setColor(myCharacter,cColor)
                end
            end

            if v.pcOColor then
                local puppetCharacter = FieldManager:getPuppetCharacter()
                if puppetCharacter then
                    local ir = (v.npcMaskTColor.r - v.pcOColor.r) * percent
                    local ig = (v.npcMaskTColor.g - v.pcOColor.g) * percent
                    local ib = (v.npcMaskTColor.b - v.pcOColor.b) * percent
                    local cColor = {r = v.pcOColor.r + ir,g = v.pcOColor.g + ig,b = v.pcOColor.b + ib}
                    display.setColor(puppetCharacter,cColor)
                end
            end

            if v.npcOColor then
                local npc = self:getNpcById(npcId)
                if npc then
                    local ir = (v.npcMaskTColor.r - v.npcOColor.r) * percent
                    local ig = (v.npcMaskTColor.g - v.npcOColor.g) * percent
                    local ib = (v.npcMaskTColor.b - v.npcOColor.b) * percent
                    local cColor = {r = v.npcOColor.r + ir,g = v.npcOColor.g + ig,b = v.npcOColor.b + ib}
                    display.setColor(npc,cColor)
                end
            end

            if iTime == v.npcMaskFadeTime then
                self:singleNpcMaskOverCall(npcId)
            end

        end
    end
end


function NpcManager:fadeOutExceptFocusNpc(focusNpcId)
    local action = cc.Sequence:create(cc.FadeTo:create(0.2,0))
    for k,npc in pairs(self._npcs) do
        if not cc.isDead(npc)
          and ToNumber(npc:getId()) ~= ToNumber(focusNpcId) then
            npc:excuteAction(action:clone())
        end
    end
end

function NpcManager:fadeInNpcs()
    local action = cc.Sequence:create(cc.FadeTo:create(0.2,255))
    for k,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:excuteAction(action:clone())
        end
    end
end

function NpcManager:allNpcUpHeadNodeVisibility(visible,isWithLegion)
    for k,npc in pairs(self._npcs) do
        if not cc.isDead(npc) then
            npc:setWithLegion(isWithLegion)
            NpcAllNameNodeVisibility(npc,visible)
            NpcCurrentMarkVisibility(npc,visible)
        end
    end
end

function NpcManager:stopUpdateNpcPlayEvents(isStop)
    self._stopUpdateNpcPlayEvents = isStop
    if isStop then
        self:allNpcUpHeadNodeVisibility(false)
    end
end

function NpcManager:isStopUpdateNpcPlayEvents()
    return self._stopUpdateNpcPlayEvents
end

function NpcManager:stopUpdateNpcPlayEventsForTeam(isStop)
    self._stopUpdateNpcPlayEventsForTeam = isStop
    if isStop then
        self:allNpcUpHeadNodeVisibility(false)
    end
end

function NpcManager:updateNpcVisibilityByWithTeam(withTeam)
    if self._stopUpdateNpcPlayEventsForTeam ~= withTeam then 
        self:stopUpdateNpcPlayEventsForTeam(withTeam)
    end
end

function NpcManager:isStopUpdateNpcsEvent()

    if StoryManager:isEnterStoryMode()
    or self:isStopUpdateNpcPlayEvents()
    or self._stopUpdateNpcPlayEventsForTeam
    or FieldManager:isTransferring()
    or DoEventHandler.isPlayVideoEvent() then
        return true
    end
    return false
end

function NpcManager:updateNpcInSight(npc,lx,rx)
    local npcx = NodeGetPositionXFunc(npc)
	if npcx < lx or npcx > rx then
        if npc:outSight() then
            npc:hideSelf()
            NpcStopShowUpHead(npc,true)
        end
	else
        if npc:intoSight() then
            NpcStopShowUpHead(npc,false)
            npc:showSelf()
        end
	end
end

function NpcManager:excuteNpc(character,npc,cx,isLeader,isWithTeam,isDungeonArea)
    if not npc then
        return
    end

    NpcAdjustSounds(npc,cx)
    NpcUpdateHeadSignMark(npc,cx)

    if NpcIsPlayEvents(npc) then
        local battleOrder = npc:getBattleOrder()
        local isStart = self:isEventsOrderStart(isDungeonArea,battleOrder)
        if npc:getTaskId() 
            and UD:isTaskTaken(npc:getTaskId()) and not npc:isGatherType() then
            if isStart then
                local isRange = npc:isTargetInTalkRange(cx)
                if isRange then
                    self:doPlayTaskEvents(npc,character)
                else
                    self:moveToTargetNpc(npc)
                end
            end
        else
            local eventsVec = npc:getEventsByFlag("talk")
            if not eventsVec then
                eventsVec = npc:getEventsFrom(1)
            end
            if eventsVec then
                if isStart then
                    local isRange = npc:isTargetInTalkRange(cx)
                    if isRange then
                        self:doPlayEvents(npc,character,eventsVec)
                    else
                        self:moveToTargetNpc(npc)
                    end
                end
            end
        end
    end
end

function NpcManager:moveToTargetNpc(npc,callback)
    local targetX = MathFloor(self:getTargetToNpc(npc))
    local cx = MathFloor(FieldManager:getCharacterX())
    if targetX ~= cx then
        FieldManager:setTargetX(targetX)
    else
        if callback then
            callback()
        end
    end
end

local NpcManager_updateNpcInSight = NpcManager.updateNpcInSight
local NpcManager_excuteNpc = NpcManager.excuteNpc

function NpcManager:updateNpcLower(dt)
    local character = FieldManager:getMyCharacter()
    if cc.isDead(character) then return end
    local cx = FieldManager:getCharacterX()
    local cameraX = MapManager:getCameraX()+display.cx
    local leftBound = cameraX - display.width * 2
    local rightBound = cameraX + display.width * 2
    local stopUpdate = self:isStopUpdateNpcsEvent()
    local isDungeonArea = DungeonManager:isDungeonArea(cx)
    local npcVec = self._npcs
    for uid,npc in pairs(npcVec) do
        NpcManager_updateNpcInSight(self,npc,leftBound,rightBound)
        if NodeIsVisibleFunc(npc) then
            NpcUpdateFrameLower(npc,dt)
            if not stopUpdate then
                NpcManager_excuteNpc(self,character,npc,cx,nil,nil,isDungeonArea)
            end

            -- 如果当前的 npc头顶交互 是隐藏的状态，就把该npc的交互隐藏（暂时只添加组队跟随的情况，其他情况有发现再添加）
            if self._stopUpdateNpcPlayEventsForTeam then
                NpcCurrentMarkVisibility(npc, false)
            end

            -- 剧情里隐藏所有npc头顶交互按钮
            if StoryManager:isEnterStoryMode() then
                NpcCurrentMarkVisibility(npc, false)
            end
        end
    end

    -- 是否停止触发任务剧情
    local function isStopExecuteTaskStory()
        if TeamModel:isWithTeam() then return true end -- 玩家跟随时，不触发剧情
        if DungeonManager:isInDungeon() and not DungeonManager:isTaskDungeon() then -- 非剧情副本，也不触发剧情
            return true
        end
        return false
    end

    if isStopExecuteTaskStory() then
        self:updateNpcMask(dt)
        return
    end

    local areaVec = self:getAreaVec()
    for k,v in pairs(areaVec) do
        local area = v
        if area and not AreaIsPlayingEvents(area) and not AreaIsPublic(area) then
            local nlx,nrx = area:getMinX(),area:getMaxX()
            if cx >= nlx and cx <= nrx then
                local eventsVec = area:getEventsFrom(1)
                if eventsVec then
                    local battleOrder = area:getBattleOrder()
                    local isStart = self:isEventsOrderStart(isDungeonArea,battleOrder)
                    if isStart then
                        FieldManager:setTrackDoing(false)
                        area:playingEvents(true)
                        FieldManager:characterStopMove()
                        character:faceTo(area:getCenterX())
                        local len = #eventsVec
                        if len > 0 then
                            DoEventHandler.startEvents(clone(eventsVec),area:getId())
                            break
                        end
                    end
                elseif area:getTaskId()
                        and UD:isTaskTaken(area:getTaskId())
                        and ScriptManager:isHaveTaskEvents(area:getTaskId()) then
                    local battleOrder = area:getBattleOrder()
                    local isStart = self:isEventsOrderStart(isDungeonArea,battleOrder)
                    if isStart then
                        TaskManager:setTaskNpcId(area:getId())
                        FieldManager:setTrackDoing(false)
                        area:playingEvents(true)
                        FieldManager:characterStopMove()
                        character:faceTo(area:getCenterX())
                        DoEventHandler.startSingleTaskEvents(area:getTaskId())
                    end
                end
            end
        end
    end

    self:updateNpcMask(dt)
end

function NpcManager:updateNpcPlayEvents(dt)
    local character = FieldManager:getMyCharacter()
    if not character then return end

    self.m_Trigger = false

    local stopUpdate = self:isStopUpdateNpcsEvent()
    if not stopUpdate then 
        FieldManager:deletePuppetCharacter()
    end

    local npcVec = self._npcs
    for uid,npc in pairs(npcVec) do
        if NodeIsVisibleFunc(npc) then
            NpcUpdateFrame(npc,dt)
        end
    end

    self:updateShowEventFlag()
    
    if stopUpdate then return end

    if self:isDebugForVideo() then
        self:allNpcUpHeadNodeVisibility(false)
    end
end

function NpcManager:isEventsOrderStart(isDungeonArea,battleOrder)
    local isStart = false
    if isDungeonArea and battleOrder == 0 then
        isStart = false
    else
        isStart = true
    end
    return isStart
end

function NpcManager:setShowEventFlag(isShow)
    if self._showEventFlag ~= isShow then
        self._showEventFlag = isShow
    end
end

function NpcManager:setShowEventNpc(npc)
    if self._showEventNpc ~= npc then
        self._showEventNpc = npc
    end
end

function NpcManager:getShowEventNpc()
    return self._showEventNpc
end

function NpcManager:isShowEventFlag()
    return self._showEventFlag
end

function NpcManager:updateShowEventFlag()
    if not self.m_Trigger then
        self:setShowEventFlag(false)
        self:setShowEventNpc(nil)
    end
end

function NpcManager:doPreEvents(npc,character)
    FieldManager:setTrackDoing(false)
    npc:setDoingEvents(true)
    npc:setPlayEvents(false)
    npc:standBy()
    npc:stopMove()
    FieldManager:characterStopMove()
    character:faceTo(npc:getPositionX())
    if npc:isFaceTo() then
        npc:faceToCharacter(character)
    end
end

function NpcManager:doPlayEvents(npc,character,events)
    if not events then return end
    self:doPreEvents(npc,character)
    local len = #events
    if len > 0 then
        DoEventHandler.setCurrrentMark(npc:getMark())
        DoEventHandler.startEvents(clone(events),npc:getId())
    end
end

function NpcManager:doPlayTaskEvents(npc,character)
    self:doPreEvents(npc,character)

    -- 检查是否是有分支，并且弹窗
    local taskId = npc:getTaskId()
    local taskConfig = GD:queryTaskById(taskId)
    local nextTasks = string.split(taskConfig.nextTask, ";")
    if tonumber(taskConfig.taskCreate) == 6 and #nextTasks > 1 then
        app:sendMsg("TaskController", "showBranchTaskDialog", {taskId = taskId, nextTasks = nextTasks})
        npc:setDoingEvents(false)
        return
    end

    TaskManager:setTaskNpcId(npc:getId())
    DoEventHandler.startSingleTaskEvents(npc:getTaskId())
end

function NpcManager:hideAllNpcMark()
    self:allNpcUpHeadNodeVisibility(false)
    -- self:mapNpcMarkVisibility(false)
    self:stopUpdateNpcPlayEvents(true)
end

function NpcManager:showAllNpcMark()
    self:allNpcUpHeadNodeVisibility(true)
    -- self:mapNpcMarkVisibility(true)
    self:stopUpdateNpcPlayEvents(false)
end

function NpcManager:allNpcExceptUiNpcVisibility(visible)
    self:stopUpdateNpcPlayEvents(not visible)
    self:mapNpcExceptUiNpcVisibility(visible)
end

function NpcManager:allNpcAbslutelyVisibility(visible)
    self:stopUpdateNpcPlayEvents(not visible)
    self:allMapNpcsVisibility(visible)
end

function NpcManager:allNpcNameNodeVisibility(visible)
    self:mapNpcNameNodeVisibility(visible)
    self:stopUpdateNpcPlayEvents(not visible)
end

function NpcManager:markClick(npc)
    -- NPC头顶UI交互点击响应方法
    if (not npc) or self:isNpcExcuteEvents() then return end
    if not DoEventHandler.isGather() 
        and not MapManager:isChangeMap() then
        if TeamModel:isWithTeam() then
            local params = npc:getSingleParams()
            if params and params.tag == 1 then
                local dialogKey = "key_single_dialog"
                local dialog = display.newMessageBox(params.say)
                UIHandler:addPopupUi(dialog,dialogKey)
                return
            end
        end

        local function setExcuteParams(npc)
            npc:setPlayEvents(true)
            npc:setDoingEvents(true)
            FieldManager:setTrackDoing(true)
        end
        if npc:getMarkType() == "task" then
            -- TaskManager:setTaskNpcId(npc:getId())
            -- DoEventHandler.startSingleTaskEvents(UD:getCurTaskId())
            setExcuteParams(npc)
        elseif npc:getMarkType() == "gather" then
            npc:addEventsAt(1,St.createTaskGatherEvents(npc:getId(), npc:getUid()))
            setExcuteParams(npc)
        elseif npc:getMarkType() == "common" then
            if not npc:getConfig() then return end
            local npcConfig = npc:getConfig()
            local fType = tonumber(npcConfig["function"])
            local fPar = npcConfig["functionPar"]
            if fType == 1 then
                -- 打开功能界面 走来源跳转
                -- TODO: fPar 解析 1 功能主界面ID|功能子界面ID
                local resource = GD:getSpecialResource(fPar)
                if resource then
                    display.resourceGotoFeature({feature = resource[1], param = resource[3]})
                end
            elseif fType == 51 then
                -- 定点传送
                -- 服务器根据传送入口NPC来决定传送的地图和位置
                local array = string.split(fPar,"|")
                npc:addEventsAt(1,St.createSwitchMapEvents(tonumber(array[1]), npc:getId()))
                setExcuteParams(npc)
            elseif fType == 53 then
                -- 副本传送
                -- 这个功能是为剧情副本服务，剧情副本通关后，入口NPC就关闭了。副本入口统一在UI界面里
                local array = string.split(fPar,";")
                if #array >= 2 then
                    local change = false
                    local dArray = string.split(array[1],"|")
                    dump(dArray)
                    if #dArray >= 3 then
                        local taskId = tonumber(dArray[1])
                        if UD:getCurTaskId() <= taskId then
                            change = true
                            local dungeonId = tonumber(dArray[2])
                            local dungeonSubId = tonumber(dArray[3])
                            npc:stopShowUpHead(true)
                            npc:currentMarkVisibility(false)

                            self._dungeonNpc = npc
                            self:reqEnterTaskDungeon(dungeonId, dungeonSubId)
                        end
                    end
                    if not change then
                        local resource = GD:getSpecialResource(array[2])
                        if resource then
                            display.resourceGotoFeature({feature = resource[1], param = resource[3]})
                        end
                    end
                end
            elseif fType == 54 then
                -- 同地图传送
                UIHandler:showBlinkBlackScreen(0.5)
                MapManager:requestTpInSameMap(npc:getId())
            elseif fType == 2 then
                -- 灰烬入侵战斗入口
                BattleManager:grayBattleStart({battleId = npc:getBattleId(), mineId = npc:getMineId(), x = npc:getOriginalX(),})
            elseif fType == 61 then
--                display.pushToast(L("进入训练场"))
                BattleManager:reqTraining(Enums.trainingType.build)
            end
        else
            setExcuteParams(npc)
--            npc:updateGuideAnimationTimes()
        end
        app:dispatchCustomEvent("EVENT_STOP_TRACKING")
    end
end

-- 进入剧情副本，有队伍需要主动退出队伍
function NpcManager:reqEnterTaskDungeon(dungeonId, dungeonSubId)
    local hasTeam = app:sendMsg("TeamModel", "hasTeam")
    if hasTeam then
        app:sendMsg("TeamModel", "reqQuitTeam")
    end

    DungeonManager:enterDungeonRequest(dungeonId, dungeonSubId, nil, nil, function()
        if not cc.isDead(self._dungeonNpc) then
            self._dungeonNpc:stopShowUpHead(false)
            self._dungeonNpc:currentMarkVisibility(true)
        end
    end)
end

-- 弃用
function NpcManager:updateSecretNpcs()

end

function NpcManager:isHideSecretNpcs(isLeader,isWithTeam)
    if MapManager:isSecretMap() then 
        if not isWithTeam and not isLeader then
            return true
        else
            return false
        end
    end
    return false
end

function NpcManager:isCanTrackNpc(npcId)
    local limitLv = 0
    local needTaskId = 0
    local npcConfig = GD:queryMapNpc(npcId)
    if npcConfig then
        limitLv = ToNumber(npcConfig.showMinLv) or 0
        needTaskId = ToNumber(npcConfig.showMinQuset) or 0
    end

    local curLevel = UD:getPlayer().level
    local curMainTaskId
    local task = UD:getCurTask()
    if task then
        curMainTaskId = task:getId()
    else
        curMainTaskId = needTaskId
    end

    if curMainTaskId < needTaskId then
        local taskName = ""
        app:popMessage("shownpc", {taskName})
        return false
    end
    if curLevel < limitLv then
        app:popMessage("limitLevel", {limitLv})
        return false
    end
    return true
end

function NpcManager:updateMercNpc()
    if not MapManager:isInHome() then return end
    local configs = GD:queryPartners()
    
    for k,v in pairs(configs) do
        if v.npcShow then
            local npcId = ToNumber(v.npcShow)
            local parter = UD.partnersData:getPartnerById(ToNumber(v.ID))
            if not parter then
                self:deleteNpcById(npcId)
            else
                if parter:isEquiped() then
                    self:deleteNpcById(npcId)
                else
                    local config = GD:queryMapNpc(npcId)
                    local npcUid = self:generateNpcUid()
                    NpcFactory:createNpc(config,npcUid,nil,nil,true)
                end
            end
        end
    end
end

function NpcManager:getTargetToNpc(npc)
    if not npc then return end
    local cx = npc:getPositionX()
    local er = npc:getEventRange()
    local targetX = cx
    if er then
        if cx < cx - er.radius or cx > cx + er.radius then
            if cx < nx then
                targetX = cx - er.radius + 5
            else
                targetX = cx + er.radius - 5
            end
        end
    end
    return targetX
end

function NpcManager:getTriggerXToNpc(npcId,destMapId,fromBorn,dx)
    if not npcId then return 600 end
    local cx = 0
    if MapManager:getMapId() == destMapId then
        cx = FieldManager:getCharacterX()
    else
        local destMapConfig = GD:queryMapById(destMapId)
        if destMapConfig then
            local npc
            if fromBorn then
                npc = GD:queryMapNpc(ToNumber(destMapConfig.Bornat))
            else
                npc = GD:queryMapNpc(ToNumber(destMapConfig.stonebornat))
            end
            cx = ToNumber(npc.coordinate)
        end
    end
    local targetX = cx
    local npcConfig = GD:queryMapNpc(npcId)
    if npcConfig then
        local nx = (dx == nil) and ToNumber(npcConfig.coordinate) or dx
        local mapNpc = self:getNpcById(npcId)
        if mapNpc then
            nx = mapNpc:getPositionX()
        end
        local radius = ToNumber(npcConfig.talkRadius)
        if cx < nx - radius or cx > nx + radius then
            if cx < nx then
                targetX = nx - radius + 5
            else
                targetX = nx + radius - 5
            end
        end
    end
    return targetX
end

function NpcManager:getBornPointX(npcId,destMapId,fromBorn)
    local cx = 600
    if npcId then
        local destMapConfig = GD:queryMapById(destMapId)
        if destMapConfig then
            local npc
            if fromBorn then
                npc = GD:queryMapNpc(ToNumber(destMapConfig.Bornat))
            else
                npc = GD:queryMapNpc(ToNumber(destMapConfig.stonebornat))
            end
            cx = npc and ToNumber(npc.coordinate) or cx
        end     
    end
    return cx
end

function NpcManager:getNpcMapCoordinate(npcId)
    local npc = GD:queryMapNpc(npcId)
    if npc then
        return ToNumber(npc.coordinate)
    else
        return 0
    end
end

function NpcManager:getMapBornPointCoordinate(mapId)
    local coordinate = 600

    local mapConfig = GD:queryMapById(mapId)
    if mapConfig then
        local npc = GD:queryMapNpc(ToNumber(mapConfig.Bornat))
        if npc then
            coordinate = ToNumber(npc.coordinate)
        end
    end

    return coordinate
end

function NpcManager:getHintTriggerRangeToNpc(npcId, pos)
    local lx = 0 
    local rx = 0 
    local nx = 0
    local tlx = 0
    local trx = 0
    local radius = 0
    local talkRadius = 0
    local npcConfig = GD:queryMapNpc(npcId)

    if npcConfig then
        nx = pos or ToNumber(npcConfig.coordinate) or 0
        radius = ToNumber(npcConfig.eventRadius) or 100
        talkRadius = ToNumber(npcConfig.talkRadius) or 100
        lx = nx - radius
        rx = nx + radius
        tlx = nx - talkRadius
        trx = nx + talkRadius
    end
    return lx,rx,nx,radius,talkRadius,tlx,trx
end

function NpcManager:getWayNpcId(taskConfig)
    if not taskConfig then
        print("getWayNpcId taskConfig is nil.")
        return
    end
    local trackType = tonumber(taskConfig.scheduleString)
    if trackType == 5 then
        -- 追踪界面
        return
    end

    local findNpcId = 0

    local minesC = TaskManager:getTaskMinesConfigs(taskConfig)
    if trackType == 3 and minesC and #minesC > 0 then
        -- 追踪暗雷区域
        local disx = 0
        local index = 1
        for i,mineC in ipairs(minesC) do
            if mineC and mineC.ID then
                local coordinate = tonumber(mineC.coordinate)
                local tDisx = math.abs(coordinate - FieldManager:getCharacterX())
                if disx == 0 or tDisx <= disx then
                    disx = tDisx
                    index = i
                end
            end
        end
        findNpcId = minesC[index]
    else
--        local oreType = tonumber(taskConfig.oreType)
--        if oreType then
--            local ox = FieldManager:getCharacterX()
--            if ox then
--                findNpcId = UD:getNearestResourcePointByType(oreType,MapManager:getMapId(),ox)
--            end
--        end

--        if findNpcId == 0 then
            if trackType == 1 then
                -- 追踪地图base npc
                findNpcId = tonumber(taskConfig.autoRun)
            elseif trackType == 6 then
                -- 追踪护送目的地
                local mapLocationInfo = string.split(taskConfig.autoRun,"|")
                if #mapLocationInfo >= 2 then
                    findNpcId = {mapId = tonumber(mapLocationInfo[1]),x = tonumber(mapLocationInfo[2])}
                end
            else
                -- 追踪任务采集物2或追踪采集物4
                local npcs = string.split(taskConfig.autoRun,";")
                local npcExisted
                for i,v in ipairs(npcs) do
                    local npcId = tonumber(v)
                    npcExisted = self:getNpcById(npcId)
                    if npcExisted and not npcExisted:isTaskGathered() then
                        findNpcId = npcId
                        break
                    end
                end
                if npcExisted == nil then
                    for i,v in ipairs(npcs) do
                        local npcId = tonumber(v)
                        findNpcId = npcId
                        break
                    end
                end
            end
--        end
    end
    return findNpcId
end

function NpcManager:setFadeNpcsForBattle(isFade)
    self._fadeNpcsForBattle = isFade
end

function NpcManager:isFadeNpcsForBattle()
    return self._fadeNpcsForBattle
end

function NpcManager:setDebugForVideo(isDebugForVideo)
    self._debugForVideo = isDebugForVideo
end

function NpcManager:isDebugForVideo()
    return self._debugForVideo
end

function NpcManager:generateNpcUid()
    self._uidGenerator = self._uidGenerator + 1
    return npcUidClientPrefix..tostring(self._uidGenerator)
end

function NpcManager:setGrayBattleNpcUidList(list)
    self._grayBattleNpcUidList = list
end

function NpcManager:updateGrayBattleNpcs()
    for _, uid in ipairs(self._grayBattleNpcUidList) do
        self:deleteNpcByUid(uid)
    end
    self._grayBattleNpcUidList = NpcFactory:createGrayBattleNpcs(MapManager:getMapId(), false)
end

------- 更新采集点 ------------

function NpcManager:setResourcePointNpcUidList(list)
    self._resourcePointNpcUidList = list
end

-- 服务器批量下发的采集点信息，只下发新增的，采集完后需要本地移除
function NpcManager:onUpdatePickPoint(updatePoints)
    local addNpcs = {}
    for _, value in ipairs(updatePoints) do
        local point = NPC.new()
        point:read(value)
        UD.npcResourcePoints[point.id] = point
        table.insert(addNpcs, point.id)
    end

    -- 不再全量更新，只更新新增的点
    for i, id in ipairs(addNpcs) do
        local npc = self:getNpcById(id)
        if npc then
            print("Error: server UpdatePickPoint repeat! npcId = ", id)
        else
            local npcConfig = GD:queryMapNpc(id)
            if npcConfig and tonumber(npcConfig.mapId) == tonumber(MapManager:getMapId()) then
                local npcUid = self:generateNpcUid()
                NpcFactory:createNpc(npcConfig, npcUid, false, nil, true)
                table.insert(self._resourcePointNpcUidList, npcUid)
            end
        end
    end
end

-- 移除采集点
function NpcManager:removePickPointByNpcId(npcId)
    print("removePickPointByNpcId", npcId)
    UD.npcResourcePoints[npcId] = nil

    -- 不再全量更新，只移除当前采集点
    local npc = self:getNpcById(npcId)
    if not npc then return end
    local npcUid = npc:getUid()

    self:deleteNpcByUid(npcUid)
    for i = #self._resourcePointNpcUidList , 1, -1 do 
        if self._resourcePointNpcUidList[i] == npcUid then
            table.remove(self._resourcePointNpcUidList, i)
        end
    end
end 

function NpcManager:updateResourcePointNpcs()
    for _, uid in ipairs(self._resourcePointNpcUidList) do
        self:deleteNpcByUid(uid)
    end
    self._resourcePointNpcUidList = NpcFactory:createAllResourcePoints(MapManager:getMapId(), false)
end

function NpcManager:getNearestGrayBattleNpc(currentPos)
    local nearestDistance = math.huge
    local nearestNpc
    local npcPos = 0
    for _, npcUid in ipairs(self._grayBattleNpcUidList) do
        local npc = self:getNpcByUid(npcUid)
        npcPos = npc:getOriginalX()
        if math.abs(npcPos - currentPos) < nearestDistance then
            nearestDistance = math.abs(npcPos - currentPos)
            nearestNpc = npc
        end
    end
    return nearestNpc
end

-- 进入剧情时，设置配置中默认隐藏的npc
function NpcManager:hideNpcByConfigInStory()
    self.hideNpcs = {} -- 这里存储npcId 还是 直接存储npc ？？
    local npcVec = self._npcs
    for uid,npc in pairs(npcVec) do
        local config = self:getNpcConfigById(npc:getId())
        if config and config.storyNpcShow and tonumber(config.storyNpcShow) == 1 then
            table.insert(self.hideNpcs, {npc = npc, visible = NodeIsVisibleFunc(npc)})
            npc:setVisibleInEvent(false)
        end
    end
end

-- 离开剧情时，显示进入剧情时隐藏的npc
function NpcManager:showNpcByConfigOutStory()
    if not self.hideNpcs or #self.hideNpcs == 0 then return end
    for _, npcData in ipairs(self.hideNpcs) do
        local npc = npcData.npc
        local visible = npcData.visible
        if not cc.isDead(npc) then
            npc:setVisibleInEvent(visible)
            npc:setEventVisibility(false)
        end
    end
    self.hideNpcs = {}
end

return NpcManager